# coding:utf-8
import time, threading

class SnowFlake(object):
    """
    @desc 雪花算法·生成自增ID
    """

    # 开始时间截 (2019-01-01)
    _twepoch = 1546272000000
    # 机器id所占的位数
    _workerIdBits = 5
    # 数据标识id所占的位数
    _datacenterIdBits = 5
    # 支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    _maxWorkerId = -1 ^ (-1 << _workerIdBits)
    # 支持的最大数据标识id，结果是31
    _maxDatacenterId = -1 ^ (-1 << _datacenterIdBits)
    # 序列在id中占的位数
    _sequenceBits = 12
    # 机器ID向左移12位
    _workerIdShift = _sequenceBits
    # 数据标识id向左移17位(12+5)
    _datacenterIdShift = _sequenceBits + _workerIdBits
    # 时间截向左移22位(5+5+12)
    _timestampLeftShift = _sequenceBits + _workerIdBits + _datacenterIdBits
    # 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095
    _sequenceMask = -1 ^ (-1 << _sequenceBits)
    # 工作机器ID(0~31)
    _workerId=0
    # 数据中心ID(0~31)
    _datacenterId=0
    # 毫秒内序列(0~4095)
    _sequence = 0
    # 上次生成ID的时间截
    _lastTimestamp = -1
    lock = threading.Lock()

    def __init__(self, workerId=0, datacenterId=0):
        if workerId > self._maxWorkerId or workerId < 0:
            raise Exception('worker Id can\'t be greater than %d or less than 0' % self._maxWorkerId)
        if datacenterId > self._maxDatacenterId or datacenterId < 0:
            raise Exception('datacenter Id can\'t be greater than %d or less than 0' % self._maxDatacenterId)
        self._workerId = workerId
        self._datacenterId = datacenterId

    def nextMillis(self, lastTimestamp):
        timestamp = int(round(time.time() * 1000))
        while timestamp <= lastTimestamp:
            timestamp = int(round(time.time() * 1000))
        return timestamp

    def nextId(self):
        timestamp = int(round(time.time() * 1000))
        if timestamp < self._lastTimestamp:
            raise Exception("Clock moved backwards.  Refusing to generate id for %d milliseconds" % (self._lastTimestamp - timestamp))
        if self._lastTimestamp == timestamp:
            self._sequence = (self._sequence + 1) & self._sequenceMask
            if self._sequence == 0:
                timestamp = self.nextMillis(self._lastTimestamp)
        else:
            self._sequence = 0

        self._lastTimestamp = timestamp

        return ((timestamp - self._twepoch) << self._timestampLeftShift)| (self._datacenterId << self._datacenterIdShift) | (self._workerId << self._workerIdShift) | self._sequence

    def get_id(self):
        self.lock.acquire()
        try:
            this_id = self.nextId()
            return this_id
        finally:
            self.lock.release()